#!/usr/bin/python

"""
A system is a group of structures that work together to perform functions.
"""

import sys
import re
import string
import dot_parser
from logger import logger

from utils import *
from module import IO, ModuleError, Module

class SystemError(Exception):
    """
    handling class for system exceptions
    """
    
    def __init__(self, msg):
        """
        initialize the class
        """
        logger.error("system: %s" % msg)

class System():
    """
    A system is a set of interconnected modules that work together to perform
    tasks.
    """
    #
    # class init
    #
    def __init__(self, file_name):
        """
        initialize a system
        """
        self.struct_dict = {}
        self._defn_list = []
        self._edge_list = []
        self._parent_list = []
        self._child_list = []
        self._root_list = []
        self._leaf_list = []
        self._exec_order = []
        #parse the structure definition file
        self._parse_dot(file_name)
        #create lists of nodes, connections
        self._create_parent_child_lists()
        self._create_root_list()
        self._create_leaf_list()
        self._create_exec_order()
        #add the structures to the system
        self._add_modules()
        
    #
    # public functions
    #
    def pipe_push(self, input, input_order):
        """
        add an input to the system pipe
        """
        for root in self._root_list:
            self.struct_dict[root].clear_inputs()
            
        for value, root in zip(input, input_order):
            if root in self._root_list:
                self.struct_dict[root].add_input(value)

    def pipe_step(self):
        """
        advance the system pipe
        
        This algorithm is not as efficient as possible, but its aim is to match
        the actual architecture. In the future, it may be more desirable to
        remove the generations loop for the sake of performance gains.
        """
        #do the execution for each structure
        for name in self.struct_dict:
            self.struct_dict[name].compute()
        #each child gets its inputs from its parents
        for gen in self._exec_order:
            #process each node in the current generation
            for node_name in gen:
                child = self.struct_dict[node_name]
                #clear the inputs from the previous pipe step
                child.clear_inputs()
                #get the child's inputs from its parents
                for parent in child.parents:
                    child.add_input(self.struct_dict[parent].get_result())
        #return results at leaf nodes
        result_list = []
        for leaf in self._leaf_list:
            result_list.append(self.struct_dict[leaf].get_result())
        return result_list
        
    def size(self):
        """
        return the size of the system, the length of the critical path
        """
        return len(self._exec_order)

    #
    # getters
    #
    def get_parent_list(self):
        """
        return  list of the parents in the system
        """
        return self._parent_list
        
    def get_child_list(self):
        """
        return a list of the children in the system
        """
        return self._child_list
        
    def get_root_list(self):
        """
        return a list of roots in the system
        """
        return self._root_list
        
    def get_exec_order(self):
        """
        return a list of the nodes, in order of execution
        """
        return self._exec_order
        
    def get_leaf_list(self):
        """
        return the list of leaves
        """
        return self._leaf_list

    #
    # local functions
    #
    def _parse_dot(self, file_name):
        """
        parse a system specification from a file
        """
        try:
            f = open(file_name, "r")
        except IOError, e:
            raise SystemError(e)

        dot = dot_parser.parse_dot_data(f.read())
        
        #the file wasn't formatted correctly
        if dot == None:
            raise SystemError('incorrect DOT file format')
        
        edge_list = dot.get_edge_list()
        for edge in edge_list:
            source = edge.get_source().strip('\'"')
            dest = edge.get_destination().strip('\'"')
            self._edge_list.append((source, dest))

        node_list = dot.get_node_list()
        for node in node_list:
            name = node.get_name().strip('\'"')
            if name != 'node' and name != 'edge' and name != 'graph':
                attribute = node.__get_attribute__("label").strip('\'"')
                self._defn_list.append((name, attribute))

        f.close()

    def _create_parent_child_lists(self):
        """
        create lists of parent and child nodes
        avoid duplicates
        """
        for parent, child in self._edge_list:
            if self._parent_list.count(parent) == 0:
                self._parent_list.append(parent)
            if self._child_list.count(child) == 0:
                self._child_list.append(child)
        
    def _create_root_list(self):
        """
        create the root list
        """
        for parent in self._parent_list:
            #roots are nobody's child
            if self._child_list.count(parent) == 0:
                self._root_list.append(parent) 
                
    def _create_leaf_list(self):
        """
        create the list of leaf nodes
        """
        for child in self._child_list:
            #leaves are nobody's parent
            if self._parent_list.count(child) == 0:
                self._leaf_list.append(child)
                
    def _create_exec_order(self):
        """
        create the execution order list
        """
        self._exec_order.append(self._root_list)
        self._bfs()
                
    def _bfs(self):
        """
        establish the order of execution by doing a breadth-first search
        """
        next_gen = []
        
        #find nodes in the next generation
        for node in self._exec_order[-1]:
            for parent, child in self._edge_list:
                if node == parent and child not in next_gen:
                    next_gen.append(child)

        #if there are any, add to exec_order and recurse
        if len(next_gen):
            self._exec_order.append(next_gen)
            self._bfs()
                       
    def _add_modules(self):
        """
        add a structure to the system
        """

        for name, type in self._defn_list:
            #create a structure according to its type
            if type in module_dict.keys():
                self.struct_dict[name] = Module(type)
            elif type == 'io':
                self.struct_dict[name] = IO()
            else:
                raise SystemError("invalid module " + type)
            
            #add a list of this node's parents
            parent_list = []
            for parent, child in self._edge_list:
                if child == name and parent_list.count(parent) == 0:
                    parent_list.append(parent)
            self.struct_dict[name].parents = parent_list
